//===============================================================================
// Microsoft patterns & practices
// SharePoint Guidance version 2.0
//===============================================================================
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Permissions;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.SPG.Common.Configuration;
using Microsoft.Practices.SPG.Common.Logging;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Security;

namespace Microsoft.Practices.SPG.Common.ServiceLocation
{

    /// <summary>
    /// Class that implements the ServiceLocator pattern.
    /// You can use this class to decouple classes from eachother. A class only needs to know the interface of
    /// the services it needs to consume. The service locator will find and return the corresponding implementation for
    /// that interface.
    /// It implements the service Locator interface as defined in the Common Service Locator project.
    /// </summary>
    [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
    [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]
    public static class SharePointServiceLocator
    {
        private static IServiceLocator serviceLocatorInstance;
        private static object syncRoot = new object();
        /// <summary>
        /// The current ambient container.
        /// </summary>
        public static IServiceLocator Current
        {
            get
            {
                if (serviceLocatorInstance != null)
                    return serviceLocatorInstance;

                lock(syncRoot)
                {
                    if (serviceLocatorInstance != null)
                        return serviceLocatorInstance;

                    return serviceLocatorInstance = CreateServiceLocatorInstance();
                }
            }
        }

        /// <summary>
        /// Create a new instance of the servicelocator and possibly fill it with
        /// </summary>
        /// <returns></returns>
        private static IServiceLocator CreateServiceLocatorInstance()
        {
            if (SPFarm.Local == null)
            {
                throw new NoSharePointContextException("The SharePointServiceLocator needs to run in a Sharepoint context and have access to the SPFarm.");
            }

            ServiceLocatorConfig serviceLocatorConfig = new ServiceLocatorConfig();
            IEnumerable<TypeMapping> configuredTypeMappings = serviceLocatorConfig.GetTypeMappings();

            IServiceLocator serviceLocator = CreateServiceLocatorInstance(configuredTypeMappings);
            ITypeMappingsLoader typeMappingsLoader = CreateTypeLoaderInstance(configuredTypeMappings, serviceLocator);

            typeMappingsLoader.LoadTypeMappings(serviceLocator, GetDefaultTypeMappings());
            if (configuredTypeMappings != null && typeMappingsLoader != null)
            {
                
                typeMappingsLoader.LoadTypeMappings(serviceLocator, configuredTypeMappings);
            }

            return serviceLocator;
        }

        private static IEnumerable<TypeMapping> GetDefaultTypeMappings()
        {
            List<TypeMapping> defaultTypeMappings = new List<TypeMapping>();

            defaultTypeMappings.Add(TypeMapping.Create<ILogger, SharePointLogger>());
            defaultTypeMappings.Add(TypeMapping.Create<ITraceLogger, TraceLogger>());
            defaultTypeMappings.Add(TypeMapping.Create<IEventLogLogger, EventLogLogger>());
            defaultTypeMappings.Add(TypeMapping.Create<IConfigManager, ConfigManager>());
            defaultTypeMappings.Add(TypeMapping.Create<IServiceLocatorConfig, ServiceLocatorConfig>());

            return defaultTypeMappings;
        }

        private static ITypeMappingsLoader CreateTypeLoaderInstance(IEnumerable<TypeMapping> configuredTypeMappings, IServiceLocator sl)
        {
            ITypeMappingsLoader loader = FindMappingAndCreateInstance<ITypeMappingsLoader>(configuredTypeMappings);

            if (loader == null)
            {
                loader = sl as ITypeMappingsLoader;    
            }
            return loader;
        }

        private static IServiceLocator CreateServiceLocatorInstance(IEnumerable<TypeMapping> configuredTypeMappings)
        {
            IServiceLocator serviceLocator = FindMappingAndCreateInstance<IServiceLocator>(configuredTypeMappings);

            if (serviceLocator == null)
            {
                serviceLocator = new ActivatingServiceLocator();
            }
            return serviceLocator;
        }

        private static TService FindMappingAndCreateInstance<TService>(IEnumerable<TypeMapping> configuredTypeMappings)
            where TService : class
        {
            TypeMapping mapping = FindMappingForType<TService>(configuredTypeMappings);
            if (mapping == null)
                return null;

            return (TService) ActivatingServiceLocator.CreateInstanceFromTypeMapping(mapping);
        }

        private static TypeMapping FindMappingForType<TService>(IEnumerable<TypeMapping> configuredTypeMappings)
        {
            if (configuredTypeMappings == null)
                return null;

            foreach (TypeMapping configuredMapping in configuredTypeMappings)
            {
                if(configuredMapping.FromType == typeof(TService).AssemblyQualifiedName)
                {
                    return configuredMapping;
                }
            }

            return null;
        }

        /// <summary>
        /// Replace the static instance of <see cref="Current"/> with the value of newServiceLocator.
        /// </summary>
        /// <param name="newServiceLocator">The new servicelocator to use from now on. </param>
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermissionAttribute(SecurityAction.LinkDemand, ObjectModel = true)]
        public static void SetServiceLocator(IServiceLocator newServiceLocator)
        {
            serviceLocatorInstance = newServiceLocator;
        }

        /// <summary>
        /// Replace the static instance of <see cref="Current"/> with the value of newServiceLocator and fill it's 
        /// typemappings with the value of typeMappings. The parameter value newServiceLocator must implement 
        /// <see cref="ITypeMappingsLoader"/> to allow loading of . 
        /// </summary>
        /// <param name="newServiceLocator"></param>
        /// <param name="typeMappings"></param>
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermissionAttribute(SecurityAction.LinkDemand, ObjectModel = true)]
        public static void SetServiceLocator(IServiceLocator newServiceLocator, IEnumerable<TypeMapping> typeMappings)
        {
            serviceLocatorInstance = newServiceLocator;

            ITypeMappingsLoader typeMappingsLoader = newServiceLocator as ITypeMappingsLoader;

            if (typeMappingsLoader == null)
            {
                string errorMessage = string.Format(CultureInfo.CurrentCulture,
                                                    "Parameter 'newServiceLocator' does not implement ITypeMappingsLoader. It is of type '{0}'",
                                                    newServiceLocator.GetType().FullName);
                throw new ArgumentException(errorMessage,
                                            "newServiceLocator");
            }

            typeMappingsLoader.LoadTypeMappings(newServiceLocator, typeMappings);
        }

        /// <summary>
        /// Reset the servicelocator back to the default service locator. 
        /// </summary>
        [SharePointPermissionAttribute(SecurityAction.InheritanceDemand, ObjectModel = true)]
        [SharePointPermissionAttribute(SecurityAction.LinkDemand, ObjectModel = true)]
        public static void Reset()
        {
            SetServiceLocator(null);
        }
    }
}
